/*
 * Designed and developed by 2017 skydoves (Jaewoong Eum)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.skydoves.colorpickerview;

import android.graphics.Color;
import android.graphics.Point;

/**
 * 坐标映射工具类
 * 负责将用户触摸点映射到调色板的有效区域内，根据不同类型的调色板采用不同的映射策略：
 * - 圆形HSV调色板：确保坐标在圆形区域内
 * - 线性彩虹条调色板：固定y坐标在中心，只处理x坐标
 * - 其他类型调色板：使用近似算法寻找最近的有效颜色点
 */
class PointMapper {
  /**
   * 私有构造函数，防止实例化此类
   * 此类所有方法都是静态的，用于工具类的设计模式
   */
  private PointMapper() {}

  /**
   * 获取映射后的颜色点坐标
   * 根据调色板类型采用不同的映射算法，确保返回的坐标在有效区域内
   * 
   * @param colorPickerView 颜色选择器视图实例
   * @param point 用户原始触摸点坐标
   * @return 映射后的有效颜色点坐标
   */
  protected static Point getColorPoint(ColorPickerView colorPickerView, Point point) {
    Point center = 
        new Point(colorPickerView.getWidth() / 2, colorPickerView.getMeasuredHeight() / 2);
    
    // 检查是否是圆形HSV调色板
    if (colorPickerView.isHuePalette()) {
      return getHuePoint(colorPickerView, point);
    }
    // 检查是否是线性彩虹条调色板
    else if (colorPickerView.isLinearPalette()) {
      // 对于线性调色板，直接返回点击的x坐标，y坐标固定在线性条的中心
      int y = colorPickerView.getHeight() / 2;
      // 确保x坐标在有效范围内[0, 宽度-1]
      int x = Math.max(0, Math.min(point.x, colorPickerView.getWidth() - 1));
      return new Point(x, y);
    }
    // 其他类型的调色板，使用近似算法
    return approximatedPoint(colorPickerView, point, center);
  }

  /**
   * 使用二分查找算法寻找最近的有效颜色点
   * 这是一个递归方法，用于在非标准调色板上找到最近的有颜色区域
   * 
   * @param colorPickerView 颜色选择器视图实例
   * @param start 起始点
   * @param end 目标点
   * @return 找到的有效颜色点坐标
   */
  private static Point approximatedPoint(ColorPickerView colorPickerView, Point start, Point end) {
    // 如果两点距离小于等于3像素，认为已经足够接近目标
    if (getDistance(start, end) <= 3) return end;
    
    // 计算两点中点
    Point center = getCenterPoint(start, end);
    // 获取中点的颜色值
    int color = colorPickerView.getColorFromBitmap(center.x, center.y);
    
    // 如果中点颜色是透明的，向目标点方向继续查找
    if (color == Color.TRANSPARENT) {
      return approximatedPoint(colorPickerView, center, end);
    } else {
      // 如果中点有颜色，向起始点方向继续查找
      return approximatedPoint(colorPickerView, start, center);
    }
  }

  /**
   * 获取圆形HSV调色板上的有效颜色点
   * 该方法实现了圆形区域映射算法，将用户触摸点映射到调色板的圆形区域内，
   * 确保返回的坐标不会超出圆形范围，从而获取有效的颜色值。
   * 对于圆形调色板，只有在圆形区域内的坐标才代表有效颜色。
   * 
   * @param colorPickerView 颜色选择器视图实例，提供画布尺寸信息
   * @param point 用户原始触摸点坐标，可能位于圆形区域外
   * @return 映射后的圆形区域内的有效颜色点坐标
   *         - 如果原始点在圆内，则返回原始点坐标
   *         - 如果原始点在圆外，则返回圆边界上的对应点坐标
   */
  private static Point getHuePoint(ColorPickerView colorPickerView, Point point) {
    // 计算中心点坐标 - 圆形调色板的圆心位置
    float centerX = colorPickerView.getWidth() * 0.5f;
    float centerY = colorPickerView.getHeight() * 0.5f;
    
    // 计算相对于中心的坐标
    float x = point.x - centerX;
    float y = point.y - centerY;
    
    // 计算圆形调色板的最大半径
    float radius = Math.min(centerX, centerY);
    
    // 计算触摸点到中心的距离
    double r = Math.sqrt(x * x + y * y);
    
    // 如果触摸点在圆形区域外，将其映射到圆的边界上
    if (r > radius) {
      x *= radius / r;
      y *= radius / r;
    }
    
    // 将相对坐标转换回绝对坐标
    return new Point((int) (x + centerX), (int) (y + centerY));
  }

  /**
   * 计算两点之间的中点坐标
   * 
   * @param start 起始点
   * @param end 结束点
   * @return 两点之间的中点坐标
   */
  private static Point getCenterPoint(Point start, Point end) {
    return new Point((end.x + start.x) / 2, (end.y + start.y) / 2);
  }

  /**
   * 计算两点之间的欧几里得距离
   * 
   * @param start 起始点
   * @param end 结束点
   * @return 两点之间的距离，向下取整为整数
   */
  private static int getDistance(Point start, Point end) {
    return (int)
        Math.sqrt(
            Math.abs(end.x - start.x) * Math.abs(end.x - start.x)
                + Math.abs(end.y - start.y) * Math.abs(end.y - start.y));
  }
}
